The context: Why is this problem important to solve?
The objectives: What is the intended goal?
The key questions: What are the key questions that need to be answered?
The problem formulation: What is it that we are trying to solve using data science?
There are a total of 24,958 train and 2,600 test images (colored) that we have taken from microscopic images. These images are of the following categories:
Parasitized: The parasitized cells contain the Plasmodium parasite which causes malaria
Uninfected: The uninfected cells are free of the Plasmodium parasites
Key Findings:
Model Specifications:
Model Performance:
Problem and Solution Summary:
# Mounting the drive
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
import os #to create data paths
import random #to randomly select datapoints
import numpy as np # Importing numpy for Matrix Operations
import pandas as pd # Importing pandas to read CSV files
import matplotlib.pyplot as plt # Importting matplotlib for Plotting and visualizing images
import math # Importing math module to perform mathematical operations
import cv2
from PIL import Image # Importing openCV for image processing
import seaborn as sns # Importing seaborn to plot graphs
# Tensorflow modules
import tensorflow as tf
from tensorflow import keras
from keras.models import Model
from keras.layers import Dense
from keras.preprocessing.image import ImageDataGenerator # Importing the ImageDataGenerator for data augmentation
from keras.models import Sequential, Model # Importing the sequential module to define a sequential model
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, BatchNormalization, Dropout, LeakyReLU
from keras.applications.vgg16 import VGG16
from keras.utils import img_to_array, load_img, to_categorical
from keras.callbacks import EarlyStopping, ModelCheckpoint
from keras import backend
from random import shuffle
# Defining all the layers to build our CNN Model
from keras.optimizers import Adam, SGD # Importing the optimizers which can be used in our model
from sklearn import preprocessing # Importing the preprocessing module to preprocess the data
from sklearn.model_selection import train_test_split # Importing train_test_split function to split the data into train and test
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix # Importing confusion_matrix to plot the confusion matrix
# Display images using OpenCV
from google.colab.patches import cv2_imshow # Importing cv2_imshow from google.patches to display images
# Ignore warnings
import warnings
warnings.filterwarnings('ignore')
Note:
#Storing the path of the data file from the Google drive
path = '/content/drive/MyDrive/Data Science MIT/Projects/Capstone projects/Deep Learning/Malaria Detection/Notebooks/cell_images.zip'
import zipfile as zp
#The data is provided as a zip file so we need to extract the files from the zip file
with zp.ZipFile(path, 'r') as zip_ref:
zip_ref.extractall()
The extracted folder has different folders for train and test data will contain the different sizes of images for parasitized and uninfected cells within the respective folder name.
The size of all images must be the same and should be converted to 4D arrays so that they can be used as an input for the convolutional neural network. Also, we need to create the labels for both types of images to be able to train and test the model.
Let's do the same for the training data first and then we will use the same code for the test data as well.
TRAIN:
#Storing the path of the extracted "train" folder
train_dir = '/content/cell_images/train'
#Size of image so that each image has the same size
SIZE = 64
#Empty list to store the training images after they are converted to NumPy arrays
train_images = []
#Empty list to store the training labels (0 - uninfected, 1 - parasitized)
train_labels = []
#We will run the same code for "parasitized" as well as "uninfected" folders within the "train" folder
for folder_name in ['/parasitized/', '/uninfected/']:
#Path of the folder
images_path = os.listdir(train_dir + folder_name)
for i, image_name in enumerate(images_path):
try:
#Opening each image using the path of that image
image = Image.open(train_dir + folder_name + image_name)
#Resizing each image to (64,64)
image = image.resize((SIZE, SIZE))
#Converting images to arrays and appending that array to the empty list defined above
train_images.append(np.array(image))
#Creating labels for parasitized and uninfected images
if folder_name=='/parasitized/':
train_labels.append(1)
else:
train_labels.append(0)
except Exception:
pass
#Converting lists to arrays
train_images = np.array(train_images)
train_labels = np.array(train_labels)
print(f"Shape of test images: {train_images.shape}")
print(f"Shape of test labels: {train_labels.shape}")
Shape of test images: (24958, 64, 64, 3) Shape of test labels: (24958,)
TEST:
#Storing the path of the extracted "test" folder
test_dir = '/content/cell_images/test'
#Size of image so that each image has the same size (it must be same as the train image size)
SIZE = 64
#Empty list to store the testing images after they are converted to NumPy arrays
test_images = []
#Empty list to store the testing labels (0 - uninfected, 1 - parasitized)
test_labels = []
#We will run the same code for "parasitized" as well as "uninfected" folders within the "test" folder
for folder_name in ['/parasitized/', '/uninfected/']:
#Path of the folder
images_path = os.listdir(test_dir + folder_name)
for i, image_name in enumerate(images_path):
try:
#Opening each image using the path of that image
image = Image.open(test_dir + folder_name + image_name)
#Resizing each image to (64,64)
image = image.resize((SIZE, SIZE))
#Converting images to arrays and appending that array to the empty list defined above
test_images.append(np.array(image))
#Creating labels for parasitized and uninfected images
if folder_name=='/parasitized/':
test_labels.append(1)
else:
test_labels.append(0)
except Exception:
pass
#Converting lists to arrays
test_images = np.array(test_images)
test_labels = np.array(test_labels)
print(f"Shape of train images: {train_images.shape}")
print("")
print(f"Shape of test images: {test_images.shape}")
Shape of train images: (24958, 64, 64, 3) Shape of test images: (2600, 64, 64, 3)
print(f"Shape of train labels: {train_labels.shape}")
print("")
print(f"Shape of test labels: {test_labels.shape}")
Shape of train labels: (24958,) Shape of test labels: (2600,)
train_images[0] #display 3-dimensional NumPy representation of the first image in the training data
array([[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
...,
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
...,
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]], dtype=uint8)
train_labels[0] #display the label of the first image in the training data
1
Observations and insights
The aforementioned dimensions indicate that we have a reasonably large dataset.
Each image in the training and test datasets is assigned a value of either 1 or 0 using label encoding
print(f"Train images - Min: {train_images.min()}, Max: {train_images.max()}")
print(f"Test images - Min: {test_images.min()}, Max: {test_images.max()}")
Train images - Min: 0, Max: 255 Test images - Min: 0, Max: 255
Observations and insights:
Note: Normalizing these values to the range 0-1 can help improve the training process as it ensures that the model's input features are on a similar scale.
print(f"Number of training uninfected images: {np.sum(train_labels == 0)}")
print(f"Number of training parasitized images: {np.sum(train_labels == 1)}")
print(f"Number of test uninfected images: {np.sum(test_labels == 0)}")
print(f"Number of test parasitized images: {np.sum(test_labels == 1)}")
Number of training uninfected images: 12376 Number of training parasitized images: 12582 Number of test uninfected images: 1300 Number of test parasitized images: 1300
#Normalizing the images by scaling pixel intensities to the range 0-1
train_images_normalized = train_images.astype('float32') / 255.0 #dividing the pixel values by 255 (the maximum RGB value).
test_images_normalized = test_images.astype('float32') / 255.0
# Function to visualize a few images from an array
def visualize_sample_images(images, title, n=5):
plt.figure(figsize=(10, 2))
for i in range(n):
plt.subplot(1, n, i+1)
plt.imshow(images[i])
plt.title(title)
plt.axis('off')
plt.show()
visualize_sample_images(train_images_normalized[:5], 'Train Normalized')
visualize_sample_images(test_images_normalized[:5], 'Test Normalized')
Why Normalize?
# Print the shapes of the train and test datasets
print('\nshape of Training set:', train_images_normalized.shape, train_labels.shape)
print('New shape of Test set:', test_images_normalized.shape, test_labels.shape)
shape of Training set: (24958, 64, 64, 3) (24958,) New shape of Test set: (2600, 64, 64, 3) (2600,)
Observations and insights:
plt.figure(figsize=(6, 4))
sns.barplot(x=['Train Uninfected', 'Train Parasitized', 'Test Uninfected', 'Test Parasitized'],
y=[np.sum(train_labels == 0), np.sum(train_labels == 1),
np.sum(test_labels == 0), np.sum(test_labels == 1)])
plt.title('Data Distribution')
plt.show()
Observations and insights:
CNNs for the purpose of disease detection in the healthcare domain rely on the quality and diversity of the underlying data, as well as the balance across classes.
Let's visualize the images from the train data
def visualize_data(images, labels, title, n=5):
plt.figure(figsize=(10, 2))
for i in range(n):
plt.subplot(1, n, i+1)
idx = np.random.choice(np.where(labels == 1)[0]) if title == "Parasitized" else np.random.choice(np.where(labels == 0)[0])
plt.imshow(images[idx])
plt.title(title)
plt.axis('off')
plt.show()
visualize_data(train_images, train_labels, "Parasitized")
visualize_data(train_images, train_labels, "Uninfected")
Observations and insights:
The provided visualization showcases samples of parasitized and uninfected blood cell images which are helpful in understanding the visual differences between the two classes:
Patterns in Parasitized Cells:
Patterns in Uninfected Cells:
These distinct differential visual features should be captured well by CNNs
comparing differential visual features of the 2 classes will help in informing the design of the CNN layers and the choice of filters to effectively capture the characteristics that differentiate parasitized from uninfected cells. For example:
def visualize_data_large(images, labels, title, n=6):
plt.figure(figsize=(12, 12))
for i in range(n*n):
plt.subplot(n, n, i+1)
idx = np.random.choice(np.where(labels == 1)[0]) if title == "Parasitized" else np.random.choice(np.where(labels == 0)[0])
plt.imshow(images[idx])
plt.title(title)
plt.axis('off')
plt.tight_layout()
plt.show()
visualize_data_large(train_images, train_labels, "Parasitized", 6)
visualize_data_large(train_images, train_labels, "Uninfected", 6)
Observations and insights:
this finding raises questions about the integrity of the training set
avg_parasitized = train_images[train_labels == 1].mean(axis=0)
avg_uninfected = train_images[train_labels == 0].mean(axis=0)
Mean image for parasitized vs mean image for Uninfected:
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(avg_parasitized.astype('uint8'))
plt.title('Average Parasitized Cell')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(avg_uninfected.astype('uint8'))
plt.title('Average Uninfected Cell')
plt.axis('off')
plt.show()
Observations and insights:
Comparison between Average Parasitized Cell and Average Uninfected Cell:
Note: Overall, the analysis of average images is a valuable part of exploratory data analysis, offering a visual summary that can support various stages of model development and provide insights into the data that may not be immediately apparent from individual images.
Analyze the color intensity distribution of the images
def plot_color_distribution(images, labels, title):
if title == 'Parasitized':
mask = labels == 1
else:
mask = labels == 0
color_data = images[mask].mean(axis=(1,2))
sns.histplot(color_data, bins=30, kde=True)
plt.title(f'Color Intensity Distribution in {title} Images')
plt.xlabel('Pixel Intensity')
plt.ylabel('Frequency')
plt.show()
plot_color_distribution(train_images, train_labels, "Parasitized")
plot_color_distribution(train_images, train_labels, "Uninfected")
Observations: Color Intensity Distribution Analysis
def convert_to_hsv(images):
hsv_images = []
for img in images:
hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
hsv_images.append(hsv_img)
return np.array(hsv_images)
# Convert train and test images from RGB to HSV
train_images_hsv = convert_to_hsv(train_images)
test_images_hsv = convert_to_hsv(test_images)
Check the shape of converted arrays
print(f"Shape of HSV train images: {train_images_hsv.shape}")
print(f"Shape of HSV test images: {test_images_hsv.shape}")
Shape of HSV train images: (24958, 64, 64, 3) Shape of HSV test images: (2600, 64, 64, 3)
def apply_gaussian_blur(images, kernel_size=(5, 5)):
blurred_images = []
for img in images:
blurred_img = cv2.GaussianBlur(img, kernel_size, 0)
blurred_images.append(blurred_img)
return np.array(blurred_images)
# Apply Gaussian blurring to train and test images
train_images_blurred = apply_gaussian_blur(train_images)
test_images_blurred = apply_gaussian_blur(test_images)
check the shape of blurred image arrays
#check the shape of the blurred image arrays
print(f"Shape of blurred train images: {train_images_blurred.shape}")
print(f"Shape of blurred test images: {test_images_blurred.shape}")
Shape of blurred train images: (24958, 64, 64, 3) Shape of blurred test images: (2600, 64, 64, 3)
Visulaizing some of the HSV and blurred images
# Function to visualize a few images from an array
def visualize_sample_images(images, title, n=5):
plt.figure(figsize=(10, 2))
for i in range(n):
plt.subplot(1, n, i+1)
plt.imshow(images[i])
plt.title(title)
plt.axis('off')
plt.show()
visualize_sample_images(train_images_hsv[:5], 'HSV Images')
visualize_sample_images(train_images_blurred[:5], 'Blurred Images')
Blurring
# Check if labels need encoding
print(f"Unique labels in training set: {np.unique(train_labels)}")
print(f"Unique labels in test set: {np.unique(train_labels)}")
Unique labels in training set: [0 1] Unique labels in test set: [0 1]
We need to do one-hot encoding here because each outcome is currently a vector of dimension 2.
train_labels_encoded = to_categorical(train_labels)
test_labels_encoded = to_categorical(test_labels)
Note: The Base Model has been fully built and evaluated with all outputs shown to give an idea about the process of the creation and evaluation of the performance of a CNN architecture. A similar process can be followed in iterating to build better-performing CNN architectures.
# Fixing the seed for random number generators
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
#libraries have all been imported
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(SIZE, SIZE, 3), padding = "same"))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu', padding = "same"))
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.1))
model.add(Conv2D(32, (3, 3), activation='relu', padding = "same"))
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.1))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax')) # 2 because of two classes
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 64, 64, 32) 896
max_pooling2d (MaxPooling2 (None, 32, 32, 32) 0
D)
conv2d_1 (Conv2D) (None, 32, 32, 64) 18496
max_pooling2d_1 (MaxPoolin (None, 16, 16, 64) 0
g2D)
dropout_2 (Dropout) (None, 16, 16, 64) 0
conv2d_2 (Conv2D) (None, 16, 16, 32) 18464
max_pooling2d_2 (MaxPoolin (None, 8, 8, 32) 0
g2D)
dropout_3 (Dropout) (None, 8, 8, 32) 0
flatten_1 (Flatten) (None, 2048) 0
dense_3 (Dense) (None, 256) 524544
dropout_4 (Dropout) (None, 256) 0
dense_4 (Dense) (None, 2) 514
=================================================================
Total params: 562914 (2.15 MB)
Trainable params: 562914 (2.15 MB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
model.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy'])
Using Callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fit and train our Model
history = model.fit(train_images_normalized, train_labels_encoded, epochs=20, batch_size=32, callbacks=[early_stopping, model_checkpoint], validation_split = 0.2, verbose = 1)
Epoch 1/20 624/624 [==============================] - 45s 69ms/step - loss: 0.2339 - accuracy: 0.9016 - val_loss: 0.1241 - val_accuracy: 0.9862 Epoch 2/20 624/624 [==============================] - 41s 66ms/step - loss: 0.0708 - accuracy: 0.9762 - val_loss: 0.0555 - val_accuracy: 0.9876 Epoch 3/20 624/624 [==============================] - 42s 67ms/step - loss: 0.0621 - accuracy: 0.9791 - val_loss: 0.0645 - val_accuracy: 0.9824 Epoch 4/20 624/624 [==============================] - 41s 66ms/step - loss: 0.0554 - accuracy: 0.9808 - val_loss: 0.0490 - val_accuracy: 0.9838 Epoch 5/20 624/624 [==============================] - 41s 65ms/step - loss: 0.0553 - accuracy: 0.9805 - val_loss: 0.0503 - val_accuracy: 0.9848 Epoch 6/20 624/624 [==============================] - 41s 66ms/step - loss: 0.0499 - accuracy: 0.9822 - val_loss: 0.0836 - val_accuracy: 0.9750 Epoch 7/20 624/624 [==============================] - 42s 67ms/step - loss: 0.0467 - accuracy: 0.9831 - val_loss: 0.1136 - val_accuracy: 0.9603 Epoch 8/20 624/624 [==============================] - 42s 67ms/step - loss: 0.0457 - accuracy: 0.9830 - val_loss: 0.0522 - val_accuracy: 0.9852 Epoch 9/20 624/624 [==============================] - 41s 66ms/step - loss: 0.0463 - accuracy: 0.9833 - val_loss: 0.0665 - val_accuracy: 0.9798
# Load the best model saved by ModelCheckpoint
model = keras.models.load_model('best_model.h5')
# Evaluate the model on test data
test_loss, test_accuracy = model.evaluate(test_images_normalized, test_labels_encoded)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 2s 17ms/step - loss: 0.0433 - accuracy: 0.9842 Test Loss: 0.04329649731516838, Test Accuracy: 0.9842307567596436
Plotting the confusion matrix and classification report
# Predictions
predictions = model.predict(test_images_normalized)
predictions = np.argmax(predictions, axis=1) # Convert one-hot to index
test_labels_decoded = np.argmax(test_labels_encoded, axis=1) # Convert one-hot to index
#Print results using a classification report
print(classification_report(test_labels_decoded, predictions))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictions)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
# Setting the labels to both the axes
ax.set_xlabel('Predicted labels');ax.set_ylabel('True labels');
ax.set_title('Confusion Matrix');
ax.xaxis.set_ticklabels(['uninfected', 'Paracitized'])
ax.yaxis.set_ticklabels(['uninfected', 'Paracitized'])
plt.show()
##plt.xlabel('Predicted')
##plt.ylabel('True')
##plt.show()
82/82 [==============================] - 1s 17ms/step
precision recall f1-score support
0 0.98 0.99 0.98 1300
1 0.99 0.98 0.98 1300
accuracy 0.98 2600
macro avg 0.98 0.98 0.98 2600
weighted avg 0.98 0.98 0.98 2600
Plotting the train and validation curves
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the cnn_history_1 object
accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend(['Train', 'Validation'], loc='upper left')
# Displaying the plot
plt.show()
Overall Observations: Base Model ("model")
The model has a reasonable number of parameters (over 2 million).
the dropout layers after convolution layers were incorporated in the base model in attempt to avoid the overfitting problem
The validation set is used during the training process to evaluate the model's performance on unseen data, which helps in tuning hyperparameters and avoiding overfitting.
The model achieves high training accuracy, indicating it has learned the training data well.
Validation accuracy is also high and follows the training accuracy closely, which is a positive sign that the model is generalizing well to unseen data.
in addition, because the model's performance on the validation set is less than the test set, it indicates that the model is not overfitting
The test accuracy is approximately 98%, which is excellent and indicates the model has generalized well from the training data to the test data.
However, we need to look at recall for the 'parasitized' class to assess the model's performance in terms of minimizing false negatives.
The classification report shows high precision, recall, and F1-score for both classes, which is exceptional.
Given the high performance of the base model, there may not be much room for improvement. However, in a medical context, even small improvements in recall can be significant.
Here are some suggestions:
So now let's try to build another model with few more add on layers and try to check if we can try to improve the model. Therefore try to build a model by adding few layers if required and altering the activation functions.
backend.clear_session() # Clearing the backend for new model
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
Model1 = Sequential()
Model1.add(Conv2D(32, (3, 3), activation='relu', input_shape=(SIZE, SIZE, 3), padding="same"))
Model1.add(MaxPooling2D((2, 2)))
Model1.add(Dropout(0.2))
Model1.add(Conv2D(64, (3, 3), activation='relu', padding="same"))
Model1.add(MaxPooling2D((2, 2)))
Model1.add(Dropout(0.3))
Model1.add(Conv2D(128, (3, 3), activation='relu', padding="same"))
Model1.add(MaxPooling2D((2, 2)))
Model1.add(Dropout(0.4))
# Added Fourth Convolutional Block
Model1.add(Conv2D(256, (3, 3), activation='relu', padding="same"))
Model1.add(MaxPooling2D((2, 2)))
Model1.add(Dropout(0.5))
# Flattening and Fully Connected Layers
Model1.add(Flatten())
Model1.add(Dense(128, activation='relu'))
Model1.add(Dropout(0.6))
Model1.add(Dense(2, activation='softmax')) # 2 for two classes
# Display the model's architecture
Model1.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 64, 64, 32) 896
max_pooling2d (MaxPooling2 (None, 32, 32, 32) 0
D)
dropout (Dropout) (None, 32, 32, 32) 0
conv2d_1 (Conv2D) (None, 32, 32, 64) 18496
max_pooling2d_1 (MaxPoolin (None, 16, 16, 64) 0
g2D)
dropout_1 (Dropout) (None, 16, 16, 64) 0
conv2d_2 (Conv2D) (None, 16, 16, 128) 73856
max_pooling2d_2 (MaxPoolin (None, 8, 8, 128) 0
g2D)
dropout_2 (Dropout) (None, 8, 8, 128) 0
conv2d_3 (Conv2D) (None, 8, 8, 256) 295168
max_pooling2d_3 (MaxPoolin (None, 4, 4, 256) 0
g2D)
dropout_3 (Dropout) (None, 4, 4, 256) 0
flatten (Flatten) (None, 4096) 0
dense (Dense) (None, 128) 524416
dropout_4 (Dropout) (None, 128) 0
dense_1 (Dense) (None, 2) 258
=================================================================
Total params: 913090 (3.48 MB)
Trainable params: 913090 (3.48 MB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Model1.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy'])
Using Callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fit and Train the model
history1 = Model1.fit(train_images_normalized, train_labels_encoded, epochs=20, batch_size=32, callbacks=[early_stopping, model_checkpoint], validation_split = 0.2, verbose = 1)
Epoch 1/20 624/624 [==============================] - 64s 101ms/step - loss: 0.2405 - accuracy: 0.8947 - val_loss: 0.0885 - val_accuracy: 0.9846 Epoch 2/20 624/624 [==============================] - 62s 99ms/step - loss: 0.0839 - accuracy: 0.9738 - val_loss: 0.0739 - val_accuracy: 0.9792 Epoch 3/20 624/624 [==============================] - 62s 99ms/step - loss: 0.0805 - accuracy: 0.9754 - val_loss: 0.0485 - val_accuracy: 0.9836 Epoch 4/20 624/624 [==============================] - 62s 100ms/step - loss: 0.0758 - accuracy: 0.9752 - val_loss: 0.0681 - val_accuracy: 0.9742 Epoch 5/20 624/624 [==============================] - 63s 102ms/step - loss: 0.0710 - accuracy: 0.9777 - val_loss: 0.0921 - val_accuracy: 0.9728 Epoch 6/20 624/624 [==============================] - 70s 112ms/step - loss: 0.0714 - accuracy: 0.9765 - val_loss: 0.0516 - val_accuracy: 0.9832 Epoch 7/20 624/624 [==============================] - 63s 101ms/step - loss: 0.0709 - accuracy: 0.9778 - val_loss: 0.0991 - val_accuracy: 0.9613 Epoch 8/20 624/624 [==============================] - 63s 101ms/step - loss: 0.0688 - accuracy: 0.9789 - val_loss: 0.0703 - val_accuracy: 0.9736
# Load the best model saved by ModelCheckpoint
Model1 = keras.models.load_model('best_model.h5')
# Evaluate the model on test data
test_loss, test_accuracy = Model1.evaluate(test_images_normalized, test_labels_encoded)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 3s 33ms/step - loss: 0.0521 - accuracy: 0.9831 Test Loss: 0.05211582034826279, Test Accuracy: 0.9830769300460815
Plotting the confusion matrix
# Predictions
predictions = Model1.predict(test_images_normalized)
predictions = np.argmax(predictions, axis=1)
test_labels_decoded = np.argmax(test_labels_encoded, axis=1)
#Print results using a classification report
print(classification_report(test_labels_decoded, predictions))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictions)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
82/82 [==============================] - 2s 27ms/step
precision recall f1-score support
0 0.98 0.98 0.98 1300
1 0.98 0.98 0.98 1300
accuracy 0.98 2600
macro avg 0.98 0.98 0.98 2600
weighted avg 0.98 0.98 0.98 2600
Plotting the train and the validation curves
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the history object
accuracy = history1.history['accuracy']
val_accuracy = history1.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend()
# Displaying the plot
plt.show()
Insights and Observations for Model1:
Training Process:
Classification Report:
Confusion Matrix:
Training and Validation Accuracy Graph:
Specific Observations and Model Improvement Suggestions:
Now let's build a model with LeakyRelu as the activation function
Let us try to build a model using BatchNormalization and using LeakyRelu as our activation function.
backend.clear_session() # Clearing the backend for new model
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
Model2 = Sequential() #trial
# First Convolutional Block with LeakyReLU and Batch Normalization
Model2.add(Conv2D(64, (3, 3), padding="same", input_shape=(SIZE, SIZE, 3)))
Model2.add(LeakyReLU(alpha=0.1))
Model2.add(BatchNormalization())
Model2.add(MaxPooling2D((2, 2)))
Model2.add(Dropout(0.2))
# Second Convolutional Block
Model2.add(Conv2D(128, (3, 3), padding="same"))
Model2.add(LeakyReLU(alpha=0.1))
Model2.add(BatchNormalization())
Model2.add(MaxPooling2D((2, 2)))
Model2.add(Dropout(0.3))
# Third Convolutional Block
Model2.add(Conv2D(256, (3, 3), padding="same"))
Model2.add(LeakyReLU(alpha=0.1))
Model2.add(BatchNormalization())
Model2.add(MaxPooling2D((2, 2)))
Model2.add(Dropout(0.4))
# Flattening and Fully Connected Layers
Model2.add(Flatten())
Model2.add(Dense(128))
Model2.add(LeakyReLU(alpha=0.1))
Model2.add(BatchNormalization())
Model2.add(Dropout(0.6))
Model2.add(Dense(2, activation='softmax')) # 2 for two classes
# Display the model's architecture
Model2.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 64, 64, 64) 1792
leaky_re_lu (LeakyReLU) (None, 64, 64, 64) 0
batch_normalization (Batch (None, 64, 64, 64) 256
Normalization)
max_pooling2d (MaxPooling2 (None, 32, 32, 64) 0
D)
dropout (Dropout) (None, 32, 32, 64) 0
conv2d_1 (Conv2D) (None, 32, 32, 128) 73856
leaky_re_lu_1 (LeakyReLU) (None, 32, 32, 128) 0
batch_normalization_1 (Bat (None, 32, 32, 128) 512
chNormalization)
max_pooling2d_1 (MaxPoolin (None, 16, 16, 128) 0
g2D)
dropout_1 (Dropout) (None, 16, 16, 128) 0
conv2d_2 (Conv2D) (None, 16, 16, 256) 295168
leaky_re_lu_2 (LeakyReLU) (None, 16, 16, 256) 0
batch_normalization_2 (Bat (None, 16, 16, 256) 1024
chNormalization)
max_pooling2d_2 (MaxPoolin (None, 8, 8, 256) 0
g2D)
dropout_2 (Dropout) (None, 8, 8, 256) 0
flatten (Flatten) (None, 16384) 0
dense (Dense) (None, 128) 2097280
leaky_re_lu_3 (LeakyReLU) (None, 128) 0
batch_normalization_3 (Bat (None, 128) 512
chNormalization)
dropout_3 (Dropout) (None, 128) 0
dense_1 (Dense) (None, 2) 258
=================================================================
Total params: 2470658 (9.42 MB)
Trainable params: 2469506 (9.42 MB)
Non-trainable params: 1152 (4.50 KB)
_________________________________________________________________
Model2.compile(optimizer=Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy'])
Using callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fit and train the model
history2 = Model2.fit(train_images_normalized, train_labels_encoded, epochs=20, batch_size=32, callbacks=[early_stopping, model_checkpoint], validation_split = 0.2, verbose = 1)
Epoch 1/20 624/624 [==============================] - 187s 296ms/step - loss: 0.4304 - accuracy: 0.8245 - val_loss: 0.0670 - val_accuracy: 0.9916 Epoch 2/20 624/624 [==============================] - 184s 294ms/step - loss: 0.1325 - accuracy: 0.9559 - val_loss: 0.0225 - val_accuracy: 0.9920 Epoch 3/20 624/624 [==============================] - 183s 293ms/step - loss: 0.0960 - accuracy: 0.9677 - val_loss: 0.0176 - val_accuracy: 0.9952 Epoch 4/20 624/624 [==============================] - 183s 293ms/step - loss: 0.0882 - accuracy: 0.9720 - val_loss: 0.0164 - val_accuracy: 0.9938 Epoch 5/20 624/624 [==============================] - 182s 292ms/step - loss: 0.0795 - accuracy: 0.9733 - val_loss: 0.0604 - val_accuracy: 0.9832 Epoch 6/20 624/624 [==============================] - 184s 295ms/step - loss: 0.0768 - accuracy: 0.9744 - val_loss: 0.0320 - val_accuracy: 0.9878 Epoch 7/20 624/624 [==============================] - 185s 297ms/step - loss: 0.0719 - accuracy: 0.9770 - val_loss: 0.0384 - val_accuracy: 0.9878 Epoch 8/20 624/624 [==============================] - 197s 315ms/step - loss: 0.0691 - accuracy: 0.9771 - val_loss: 0.0454 - val_accuracy: 0.9840 Epoch 9/20 624/624 [==============================] - 194s 311ms/step - loss: 0.0660 - accuracy: 0.9784 - val_loss: 0.0284 - val_accuracy: 0.9906
# Load the best model saved by ModelCheckpoint
Model2 = keras.models.load_model('best_model.h5')
# Evaluate the model on test data
test_loss, test_accuracy = Model2.evaluate(test_images_normalized, test_labels_encoded)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 6s 76ms/step - loss: 0.0735 - accuracy: 0.9773 Test Loss: 0.07351841777563095, Test Accuracy: 0.9773076772689819
Plotting the train and validation accuracy
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the history object
accuracy = history2.history['accuracy']
val_accuracy = history2.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend()
# Displaying the plot
plt.show()
Generate the classification report and confusion matrix
# Predictions
predictions = Model2.predict(test_images_normalized)
predictions = np.argmax(predictions, axis=1) # Convert one-hot to index
test_labels_decoded = np.argmax(test_labels_encoded, axis=1) # Convert one-hot to index
#Print results using a classification report
print(classification_report(test_labels_decoded, predictions))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictions)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
82/82 [==============================] - 6s 71ms/step
precision recall f1-score support
0 0.96 0.99 0.98 1300
1 0.99 0.96 0.98 1300
accuracy 0.98 2600
macro avg 0.98 0.98 0.98 2600
weighted avg 0.98 0.98 0.98 2600
Model 2 Observations and insights:
Model Architecture:
Training Performance:
Test Performance:
Classification Report and Confusion Matrix:
Training and Validation Accuracy Graph: The accuracy graph demonstrates that the model trains well with high accuracy, but there is a slight divergence between training and validation accuracy, which could be an early sign of overfitting.
Specific Observations and Model Improvement Suggestions:
False Negatives: To reduce the number of false negatives further, we can try further increasing the complexity of the model or using techniques like focal loss, which gives more weight to harder-to-classify examples.
Overfitting Check: Given the slight divergence between training and validation accuracy, monitor for overfitting by possibly introducing further regularization.
Hyperparameter Tuning: Experimenting with different values for the dropout rate and the alpha parameter of the LeakyReLU activation function may yield improvements. Additionally, a learning rate schedule could be introduced to fine-tune the learning process over epochs.
backend.clear_session() # Clearing the backend for new model
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
# Split the training data to create a validation set
X_train, X_val, y_train, y_val = train_test_split(train_images, train_labels, test_size=0.2, random_state=42)
We will analyze data augmented in 2 different ways
Augmentation Parameters for Model 3a and Normalization:
# Creating an training data generator with different augmentations
train_datagen = ImageDataGenerator(
rescale=1./255, # Normalizing the pixel values
shear_range=0.3,
rotation_range=20,
##shear_range=0.2, # Shear Intensity (Shear angle in counter-clockwise direction in degrees)
##width_shift_range=0.1, # Fraction of total width for horizontal shift
##height_shift_range=0.1, # Fraction of total height for vertical shift
brightness_range=[0.8,1.2], # Range for picking a brightness shift value
zoom_range=0.1, # Range for random zoom
vertical_flip=True # Randomly flip inputs vertically
)
# Creating the validation data generator (normalization only, no augmentation)
val_datagen = ImageDataGenerator(rescale=1./255)
Setting up the Training and Validation Generator:
# Setting up the training generator
train_generator = train_datagen.flow(
x=X_train,
y=y_train,
batch_size=64,
seed=42,
shuffle=True
)
# Setting up the validation generator
val_generator = val_datagen.flow(
x=X_val,
y=y_val,
batch_size=64,
seed=42,
shuffle=True
)
# Function to visualize images
def visualize_augmented_images(image_generator, n_images):
# Get a batch of images
images, labels = next(image_generator)
# Set up the grid
plt.figure(figsize=(10, 10))
for i in range(n_images):
plt.subplot(n_images // 4 + 1, 4, i + 1)
plt.imshow(images[i])
plt.title('Augmented Image')
plt.axis('off')
plt.tight_layout()
plt.show()
# Visualize some augmented images
visualize_augmented_images(train_generator, 8)
model3a = Sequential()
# Convolutional Block
model3a.add(Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(SIZE, SIZE, 3)))
model3a.add(BatchNormalization())
model3a.add(MaxPooling2D((2, 2)))
model3a.add(Dropout(0.2))
# Flatten and Fully Connected Layers
model3a.add(Flatten())
model3a.add(Dense(128, activation='relu'))
model3a.add(BatchNormalization())
model3a.add(Dropout(0.3))
# Output Layer for binary classification
model3a.add(Dense(1, activation='sigmoid')) # 1 output node for binary classification
model3a.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 64, 64, 32) 896
batch_normalization (Batch (None, 64, 64, 32) 128
Normalization)
max_pooling2d (MaxPooling2 (None, 32, 32, 32) 0
D)
dropout (Dropout) (None, 32, 32, 32) 0
flatten (Flatten) (None, 32768) 0
dense (Dense) (None, 128) 4194432
batch_normalization_1 (Bat (None, 128) 512
chNormalization)
dropout_1 (Dropout) (None, 128) 0
dense_1 (Dense) (None, 1) 129
=================================================================
Total params: 4196097 (16.01 MB)
Trainable params: 4195777 (16.01 MB)
Non-trainable params: 320 (1.25 KB)
_________________________________________________________________
Compile the Model
# Compile the model
model3a.compile(optimizer=Adam(learning_rate = 0.001), loss='binary_crossentropy', metrics=['accuracy'])
Using Callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fit and Train Model 3a
# Train with the first augmented data set
history3a = model3a.fit(
train_generator,
callbacks=[early_stopping, model_checkpoint],
steps_per_epoch=len(X_train) // 64, # Batch size of 64
epochs=20,
validation_data=val_generator
)
Epoch 1/20 311/311 [==============================] - 53s 165ms/step - loss: 0.6135 - accuracy: 0.6914 - val_loss: 0.8522 - val_accuracy: 0.5108 Epoch 2/20 311/311 [==============================] - 49s 159ms/step - loss: 0.3870 - accuracy: 0.8381 - val_loss: 0.5153 - val_accuracy: 0.8121 Epoch 3/20 311/311 [==============================] - 49s 159ms/step - loss: 0.2739 - accuracy: 0.8990 - val_loss: 2.6805 - val_accuracy: 0.6214 Epoch 4/20 311/311 [==============================] - 49s 157ms/step - loss: 0.2596 - accuracy: 0.9096 - val_loss: 0.5436 - val_accuracy: 0.8470 Epoch 5/20 311/311 [==============================] - 49s 158ms/step - loss: 0.2510 - accuracy: 0.9184 - val_loss: 0.6963 - val_accuracy: 0.5128 Epoch 6/20 311/311 [==============================] - 49s 159ms/step - loss: 0.2263 - accuracy: 0.9264 - val_loss: 0.2010 - val_accuracy: 0.9311 Epoch 7/20 311/311 [==============================] - 48s 155ms/step - loss: 0.2290 - accuracy: 0.9258 - val_loss: 0.2963 - val_accuracy: 0.9081 Epoch 8/20 311/311 [==============================] - 53s 169ms/step - loss: 0.2113 - accuracy: 0.9294 - val_loss: 0.2145 - val_accuracy: 0.9143 Epoch 9/20 311/311 [==============================] - 50s 162ms/step - loss: 0.2194 - accuracy: 0.9282 - val_loss: 0.2024 - val_accuracy: 0.9373 Epoch 10/20 311/311 [==============================] - 51s 163ms/step - loss: 0.2106 - accuracy: 0.9303 - val_loss: 0.2726 - val_accuracy: 0.8984 Epoch 11/20 311/311 [==============================] - 52s 166ms/step - loss: 0.2089 - accuracy: 0.9296 - val_loss: 0.1663 - val_accuracy: 0.9443 Epoch 12/20 311/311 [==============================] - 50s 162ms/step - loss: 0.1949 - accuracy: 0.9336 - val_loss: 0.2008 - val_accuracy: 0.9235 Epoch 13/20 311/311 [==============================] - 51s 163ms/step - loss: 0.1838 - accuracy: 0.9380 - val_loss: 0.2264 - val_accuracy: 0.9391 Epoch 14/20 311/311 [==============================] - 51s 163ms/step - loss: 0.1819 - accuracy: 0.9378 - val_loss: 0.1513 - val_accuracy: 0.9435 Epoch 15/20 311/311 [==============================] - 51s 164ms/step - loss: 0.1762 - accuracy: 0.9418 - val_loss: 0.1578 - val_accuracy: 0.9469 Epoch 16/20 311/311 [==============================] - 52s 166ms/step - loss: 0.1699 - accuracy: 0.9414 - val_loss: 0.1667 - val_accuracy: 0.9415 Epoch 17/20 311/311 [==============================] - 53s 171ms/step - loss: 0.1731 - accuracy: 0.9390 - val_loss: 0.1515 - val_accuracy: 0.9475 Epoch 18/20 311/311 [==============================] - 51s 162ms/step - loss: 0.1784 - accuracy: 0.9397 - val_loss: 0.1619 - val_accuracy: 0.9511 Epoch 19/20 311/311 [==============================] - 51s 163ms/step - loss: 0.1647 - accuracy: 0.9432 - val_loss: 0.1762 - val_accuracy: 0.9381
Calculating the Test Accuracy
# Load the best model saved by ModelCheckpoint
Model3a = keras.models.load_model('best_model.h5')
# Evaluate model3a on test data
test_loss, test_accuracy = model3a.evaluate(test_images_normalized, test_labels)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 1s 13ms/step - loss: 0.2452 - accuracy: 0.9238 Test Loss: 0.2451961785554886, Test Accuracy: 0.9238461256027222
Plot the train and validation accuracy
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the history object
accuracy = history3a.history['accuracy']
val_accuracy = history3a.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend()
# Displaying the plot
plt.show()
Plotting the classification report and confusion matrix
# Predictions Model 3a
predictions3a = Model3a.predict(test_images_normalized)
predictions3a = np.argmax(predictions3a, axis=1)
test_labels_decoded = np.argmax(test_labels_encoded, axis=1)
#Print results using a classification report
print(classification_report(test_labels_decoded, predictions3a))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictions3a)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
82/82 [==============================] - 1s 14ms/step
precision recall f1-score support
0 0.50 1.00 0.67 1300
1 0.00 0.00 0.00 1300
accuracy 0.50 2600
macro avg 0.25 0.50 0.33 2600
weighted avg 0.25 0.50 0.33 2600
backend.clear_session() # Clearing the backend for new model
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
Trying different Augmentation Parameters (Model3b)
# Create an instance of ImageDataGenerator with desired augmentations
train_datagen1 = ImageDataGenerator(
rescale=1./255, # Normalizing the pixel values
rotation_range=20, # Degree range for random rotations
##width_shift_range=0.2, # Range (as a fraction of total width) for random horizontal shifts
##height_shift_range=0.2, # Range (as a fraction of total height) for random vertical shifts
shear_range=0.2, # Shearing intensity
zoom_range=0.02, # Range for random zoom
horizontal_flip=True, # Randomly flip inputs horizontally
fill_mode='nearest'
)
# Creating the validation data generator (normalization only, no augmentation)
val_datagen1 = ImageDataGenerator(rescale=1./255)
Setting up the Training and Validation Generator:
# Setting up the alternative training generator
train_generator1 = train_datagen1.flow(
x=X_train,
y=y_train,
batch_size=64,
seed=42,
shuffle=True
)
# Setting up the alternative validation generator
val_generator1 = val_datagen1.flow(
x=X_val,
y=y_val,
batch_size=64,
seed=42,
shuffle=True
)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-22-a94b8ceb5884> in <cell line: 2>() 1 # Setting up the alternative training generator ----> 2 train_generator1 = train_datagen1.flow( 3 x=X_train, 4 y=y_train, 5 batch_size=64, NameError: name 'train_datagen1' is not defined
# Function to visualize images
def visualize_augmented_images(image_generator1, n_images):
# Get a batch of images
images, labels = next(image_generator1)
# Set up the grid
plt.figure(figsize=(10, 10))
for i in range(n_images):
plt.subplot(n_images // 4 + 1, 4, i + 1)
plt.imshow(images[i])
plt.title('Augmented Image')
plt.axis('off')
plt.tight_layout()
plt.show()
# Visualize some augmented images
visualize_augmented_images(train_generator1, 8)
model3b = Sequential()
model3b.add(Conv2D(32, (3, 3), activation='relu', padding="same", input_shape=(SIZE, SIZE, 3)))
model3b.add(BatchNormalization())
model3b.add(MaxPooling2D((2, 2)))
model3b.add(Dropout(0.2))
model3b.add(Flatten())
model3b.add(Dense(128, activation='relu'))
model3b.add(BatchNormalization())
model3b.add(Dropout(0.3))
model3b.add(Dense(1, activation='softmax'))
model3b.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 64, 64, 32) 896
batch_normalization (Batch (None, 64, 64, 32) 128
Normalization)
max_pooling2d (MaxPooling2 (None, 32, 32, 32) 0
D)
dropout (Dropout) (None, 32, 32, 32) 0
flatten (Flatten) (None, 32768) 0
dense (Dense) (None, 128) 4194432
batch_normalization_1 (Bat (None, 128) 512
chNormalization)
dropout_1 (Dropout) (None, 128) 0
dense_1 (Dense) (None, 1) 129
=================================================================
Total params: 4196097 (16.01 MB)
Trainable params: 4195777 (16.01 MB)
Non-trainable params: 320 (1.25 KB)
_________________________________________________________________
model3b = Sequential()
model3b.add(Conv2D(32, (3, 3), activation='relu', padding="same", input_shape=(SIZE, SIZE, 3)))
model3b.add(BatchNormalization())
model3b.add(MaxPooling2D((2, 2)))
model3b.add(Dropout(0.2))
model3b.add(Flatten())
model3b.add(Dense(128, activation='relu'))
model3b.add(BatchNormalization())
model3b.add(Dropout(0.3))
model3b.add(Dense(1, activation='sigmoid'))
model3b.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 64, 64, 32) 896
batch_normalization_2 (Bat (None, 64, 64, 32) 128
chNormalization)
max_pooling2d_1 (MaxPoolin (None, 32, 32, 32) 0
g2D)
dropout_2 (Dropout) (None, 32, 32, 32) 0
flatten_1 (Flatten) (None, 32768) 0
dense_2 (Dense) (None, 128) 4194432
batch_normalization_3 (Bat (None, 128) 512
chNormalization)
dropout_3 (Dropout) (None, 128) 0
dense_3 (Dense) (None, 1) 129
=================================================================
Total params: 4196097 (16.01 MB)
Trainable params: 4195777 (16.01 MB)
Non-trainable params: 320 (1.25 KB)
_________________________________________________________________
# Compile the model
model3b.compile(optimizer=Adam(learning_rate = 0.0001), loss='binary_crossentropy', metrics=['accuracy'])
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fitting and Training Model 3b
# Train with the second augmented data set
history3b = model3b.fit(
train_generator1,
callbacks=[early_stopping, model_checkpoint],
steps_per_epoch=len(X_train) // 64, # Batch size of 64
epochs=20,
validation_data=val_generator1,
validation_steps=len(X_val) // 64, # Batch size of 64
verbose = 1
)
Epoch 1/20 311/311 [==============================] - 46s 144ms/step - loss: 0.6800 - accuracy: 0.6487 - val_loss: 0.7113 - val_accuracy: 0.5212 Epoch 2/20 311/311 [==============================] - 45s 144ms/step - loss: 0.5847 - accuracy: 0.7054 - val_loss: 0.7637 - val_accuracy: 0.5889 Epoch 3/20 311/311 [==============================] - 45s 144ms/step - loss: 0.5232 - accuracy: 0.7501 - val_loss: 0.6099 - val_accuracy: 0.6929 Epoch 4/20 311/311 [==============================] - 44s 143ms/step - loss: 0.4726 - accuracy: 0.7813 - val_loss: 0.6073 - val_accuracy: 0.6999 Epoch 5/20 311/311 [==============================] - 45s 143ms/step - loss: 0.4334 - accuracy: 0.8083 - val_loss: 0.4523 - val_accuracy: 0.7923 Epoch 6/20 311/311 [==============================] - 46s 149ms/step - loss: 0.4026 - accuracy: 0.8182 - val_loss: 0.3723 - val_accuracy: 0.8387 Epoch 7/20 311/311 [==============================] - 45s 145ms/step - loss: 0.3789 - accuracy: 0.8316 - val_loss: 0.6235 - val_accuracy: 0.7484 Epoch 8/20 311/311 [==============================] - 45s 145ms/step - loss: 0.3605 - accuracy: 0.8455 - val_loss: 0.4317 - val_accuracy: 0.7835 Epoch 9/20 311/311 [==============================] - 46s 147ms/step - loss: 0.3369 - accuracy: 0.8572 - val_loss: 0.3084 - val_accuracy: 0.8706 Epoch 10/20 311/311 [==============================] - 44s 143ms/step - loss: 0.3256 - accuracy: 0.8630 - val_loss: 0.3631 - val_accuracy: 0.8375 Epoch 11/20 311/311 [==============================] - 45s 143ms/step - loss: 0.3065 - accuracy: 0.8706 - val_loss: 0.4242 - val_accuracy: 0.8175 Epoch 12/20 311/311 [==============================] - 45s 143ms/step - loss: 0.2940 - accuracy: 0.8800 - val_loss: 1.4045 - val_accuracy: 0.5234 Epoch 13/20 311/311 [==============================] - 45s 144ms/step - loss: 0.2876 - accuracy: 0.8851 - val_loss: 0.4250 - val_accuracy: 0.8211 Epoch 14/20 311/311 [==============================] - 44s 142ms/step - loss: 0.2863 - accuracy: 0.8803 - val_loss: 0.4701 - val_accuracy: 0.7558
# Train with the second augmented data set
history3b = model3b.fit(
train_generator1,
callbacks=[early_stopping, model_checkpoint],
batch_size=64,
epochs=20,
validation_data=val_generator1,
verbose = 1
)
Epoch 1/20 312/312 [==============================] - 44s 142ms/step - loss: 0.2730 - accuracy: 0.8894 - val_loss: 0.3333 - val_accuracy: 0.8646 Epoch 2/20 312/312 [==============================] - 46s 146ms/step - loss: 0.2618 - accuracy: 0.8954 - val_loss: 0.2727 - val_accuracy: 0.8958 Epoch 3/20 312/312 [==============================] - 45s 144ms/step - loss: 0.2560 - accuracy: 0.8960 - val_loss: 0.2775 - val_accuracy: 0.8816 Epoch 4/20 312/312 [==============================] - 45s 144ms/step - loss: 0.2514 - accuracy: 0.8994 - val_loss: 0.3483 - val_accuracy: 0.8458 Epoch 5/20 312/312 [==============================] - 45s 144ms/step - loss: 0.2482 - accuracy: 0.9025 - val_loss: 0.5643 - val_accuracy: 0.6663 Epoch 6/20 312/312 [==============================] - 47s 149ms/step - loss: 0.2450 - accuracy: 0.9017 - val_loss: 0.3374 - val_accuracy: 0.8462 Epoch 7/20 312/312 [==============================] - 45s 143ms/step - loss: 0.2367 - accuracy: 0.9068 - val_loss: 0.2573 - val_accuracy: 0.8998 Epoch 8/20 312/312 [==============================] - 45s 143ms/step - loss: 0.2282 - accuracy: 0.9098 - val_loss: 1.1893 - val_accuracy: 0.5363 Epoch 9/20 312/312 [==============================] - 45s 144ms/step - loss: 0.2281 - accuracy: 0.9121 - val_loss: 0.4334 - val_accuracy: 0.8355 Epoch 10/20 312/312 [==============================] - 45s 145ms/step - loss: 0.2237 - accuracy: 0.9143 - val_loss: 0.3218 - val_accuracy: 0.8540 Epoch 11/20 312/312 [==============================] - 44s 142ms/step - loss: 0.2170 - accuracy: 0.9139 - val_loss: 0.4781 - val_accuracy: 0.8353 Epoch 12/20 312/312 [==============================] - 45s 143ms/step - loss: 0.2218 - accuracy: 0.9142 - val_loss: 0.2977 - val_accuracy: 0.8708
Calculating the Test Accuracy
# Load the best model saved by ModelCheckpoint
Model3b = keras.models.load_model('best_model.h5')
# Evaluate model3a on test data
test_loss, test_accuracy = model3b.evaluate(test_images_normalized, test_labels)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 1s 14ms/step - loss: 0.3528 - accuracy: 0.8335 Test Loss: 0.35281237959861755, Test Accuracy: 0.8334615230560303
Plotting the classification report and confusion matrix
# Predictions Model 3b
predictions = Model3b.predict(test_images_normalized)
predictions = np.argmax(predictions, axis=1) # Convert one-hot to index
test_labels_decoded = np.argmax(test_labels_encoded, axis=1) # Convert one-hot to index
#Print results using a classification report
print(classification_report(test_labels_decoded, predictions))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictions)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
82/82 [==============================] - 1s 15ms/step
precision recall f1-score support
0 0.50 1.00 0.67 1300
1 0.00 0.00 0.00 1300
accuracy 0.50 2600
macro avg 0.25 0.50 0.33 2600
weighted avg 0.25 0.50 0.33 2600
Plot the train and validation accuracy
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the history object
accuracy = history3b.history['accuracy']
val_accuracy = history3b.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend()
# Displaying the plot
plt.show()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-26-31ae517fdfba> in <cell line: 4>() 2 3 # Extracting accuracy and validation accuracy from the history object ----> 4 accuracy = history3b.history['accuracy'] 5 val_accuracy = history3b.history['val_accuracy'] 6 NameError: name 'history3b' is not defined
Why did we want to try Augmentation?
Model Architecture:
Training Performance:
Test Performance:
Classification Report and Confusion Matrix:
Now, let us try to use a pretrained model like VGG16 and check how it performs on our data.
backend.clear_session() # Clearing the backend for new model
np.random.seed(42)
random.seed(42)
tf.random.set_seed(42)
#test
# Load VGG16 model up to the third block of layers
VG16_model = VGG16(weights='imagenet', include_top=False, input_shape=(SIZE, SIZE, 3))
# Freeze the layers of the base model to preserve learned features, prevent overfitting, and improve training efficiency
for layer in VG16_model.layers[:9]: # Freezing the first 9 layers (up to the third block). by freezing the first 9 layers,
layer.trainable = False
#Add Fully Connected Layers: this block of code is essentially creating a
#transfer learning model by leveraging the "transfer" of knowledge from a pre-trained model (VGG16)
x = Flatten()(VG16_model.output) #utput of the pre-trained VGG16 model and flattens it
# Fully connected layers
x = Dense(128, activation='relu')(x)
x = Dropout(0.1)(x) #regularization purposes, to prevent overfitting to the training data by randomly setting a fraction of input units to 0 at each update during training time.
x = Dense(64, activation='relu')(x)
x = Dropout(0.2)(x)
# Output layer
predictions = Dense(1, activation='sigmoid')(x)
# This is the model we will train
model_vgg16 = Model(inputs=VG16_model.input, outputs=predictions)
# Summary of the model
model_vgg16.summary()
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 64, 64, 3)] 0
block1_conv1 (Conv2D) (None, 64, 64, 64) 1792
block1_conv2 (Conv2D) (None, 64, 64, 64) 36928
block1_pool (MaxPooling2D) (None, 32, 32, 64) 0
block2_conv1 (Conv2D) (None, 32, 32, 128) 73856
block2_conv2 (Conv2D) (None, 32, 32, 128) 147584
block2_pool (MaxPooling2D) (None, 16, 16, 128) 0
block3_conv1 (Conv2D) (None, 16, 16, 256) 295168
block3_conv2 (Conv2D) (None, 16, 16, 256) 590080
block3_conv3 (Conv2D) (None, 16, 16, 256) 590080
block3_pool (MaxPooling2D) (None, 8, 8, 256) 0
block4_conv1 (Conv2D) (None, 8, 8, 512) 1180160
block4_conv2 (Conv2D) (None, 8, 8, 512) 2359808
block4_conv3 (Conv2D) (None, 8, 8, 512) 2359808
block4_pool (MaxPooling2D) (None, 4, 4, 512) 0
block5_conv1 (Conv2D) (None, 4, 4, 512) 2359808
block5_conv2 (Conv2D) (None, 4, 4, 512) 2359808
block5_conv3 (Conv2D) (None, 4, 4, 512) 2359808
block5_pool (MaxPooling2D) (None, 2, 2, 512) 0
flatten (Flatten) (None, 2048) 0
dense (Dense) (None, 128) 262272
dropout (Dropout) (None, 128) 0
dense_1 (Dense) (None, 64) 8256
dropout_1 (Dropout) (None, 64) 0
dense_2 (Dense) (None, 1) 65
=================================================================
Total params: 14985281 (57.16 MB)
Trainable params: 13839873 (52.79 MB)
Non-trainable params: 1145408 (4.37 MB)
_________________________________________________________________
Note on Freezing Layers:
model_vgg16.compile(optimizer=Adam(learning_rate=0.0001), loss='binary_crossentropy', metrics=['accuracy'])
using callbacks
early_stopping = EarlyStopping(monitor='val_loss', patience=5)
model_checkpoint = ModelCheckpoint('best_model.h5', monitor='val_loss', save_best_only=True)
Fit and Train the model
#test
# Train with the second augmented data set
history_vgg16 = model_vgg16.fit(
train_generator,
callbacks=[early_stopping, model_checkpoint],
steps_per_epoch=len(X_train) // 64, # Batch size of 64
epochs=20,
validation_data=val_generator,
validation_steps=len(X_val) // 64, # Batch size of 64
verbose = 1
)
Epoch 1/20 311/311 [==============================] - 614s 2s/step - loss: 0.1213 - accuracy: 0.9556 - val_loss: 0.0642 - val_accuracy: 0.9760 Epoch 2/20 311/311 [==============================] - 611s 2s/step - loss: 0.0672 - accuracy: 0.9784 - val_loss: 0.0517 - val_accuracy: 0.9800 Epoch 3/20 311/311 [==============================] - 609s 2s/step - loss: 0.0612 - accuracy: 0.9791 - val_loss: 0.0471 - val_accuracy: 0.9826 Epoch 4/20 311/311 [==============================] - 614s 2s/step - loss: 0.0589 - accuracy: 0.9803 - val_loss: 0.0415 - val_accuracy: 0.9830 Epoch 5/20 311/311 [==============================] - 613s 2s/step - loss: 0.0548 - accuracy: 0.9804 - val_loss: 0.0502 - val_accuracy: 0.9806 Epoch 6/20 311/311 [==============================] - 611s 2s/step - loss: 0.0540 - accuracy: 0.9812 - val_loss: 0.0444 - val_accuracy: 0.9826 Epoch 7/20 311/311 [==============================] - 608s 2s/step - loss: 0.0506 - accuracy: 0.9820 - val_loss: 0.0456 - val_accuracy: 0.9824 Epoch 8/20 311/311 [==============================] - 609s 2s/step - loss: 0.0506 - accuracy: 0.9830 - val_loss: 0.0429 - val_accuracy: 0.9824 Epoch 9/20 311/311 [==============================] - 605s 2s/step - loss: 0.0467 - accuracy: 0.9825 - val_loss: 0.0427 - val_accuracy: 0.9834
# Load the best model saved by ModelCheckpoint
model_vgg16 = keras.models.load_model('best_model.h5')
# Evaluate model3a on test data
test_loss, test_accuracy = model_vgg16.evaluate(test_images_normalized, test_labels)
print(f"Test Loss: {test_loss}, Test Accuracy: {test_accuracy}")
82/82 [==============================] - 30s 369ms/step - loss: 0.0486 - accuracy: 0.9796 Test Loss: 0.04861888661980629, Test Accuracy: 0.9796153903007507
Plot the train and validation accuracy
#plotting the training and validation accuracy
# Extracting accuracy and validation accuracy from the history object
accuracy = history_vgg16.history['accuracy']
val_accuracy = history_vgg16.history['val_accuracy']
# Generating a range for epochs starting from 1 to the length of the accuracy list
epochs = range(1, len(accuracy) + 1)
# Creating a plot
plt.figure(figsize=(8, 8))
# Plotting both the training accuracy and validation accuracy
plt.plot(epochs, accuracy, 'b--', label='Training Accuracy') # Blue dashed line for training accuracy
plt.plot(epochs, val_accuracy, 'r--', label='Validation Accuracy') # Red dashed line for validation accuracy
# Adding labels and title for clarity
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
# Showing the legend
plt.legend()
# Displaying the plot
plt.show()
Plotting the classification report and confusion matrix
# Predictions Model VGG16
predictionsvgg16 = model_vgg16.predict(test_images_normalized)
predictionsvgg16 = np.argmax(predictionsvgg16, axis=1) # Convert one-hot to index
test_labels_decoded = np.argmax(test_labels_encoded, axis=1) # Convert one-hot to index
#Print results using a classification report
print(classification_report(test_labels_decoded, predictionsvgg16))
# Plotting the Confusion Matrix using confusion matrix() function which is also predefined tensorflow module
confusion_matrix = tf.math.confusion_matrix(test_labels_decoded, predictionsvgg16)
f, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
confusion_matrix,
annot=True,
linewidths=.4,
fmt="d",
square=True,
ax=ax
)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
82/82 [==============================] - 30s 358ms/step
precision recall f1-score support
0 0.50 1.00 0.67 1300
1 0.00 0.00 0.00 1300
accuracy 0.50 2600
macro avg 0.25 0.50 0.33 2600
weighted avg 0.25 0.50 0.33 2600
Model Architecture:
Training
Suggested Improvements:
Improvements that can be done:
Base Model Specifications:
Base Model Accuracy Rates
- The Base Model is superior in comparison to Model1 and Model2:
- The Base model is superior to the Augmented and VGG16 models:
Base Model is therfore the 'best' model
In summary, the choice of the Base Model for malaria diagnosis is guided by its superior accuracy and recall, making it highly reliable for medical use. While it has more trainable parameters than some simpler models, it remains computationally feasible and does not excessively burden the available resources, making it a suitable choice for deployment in diverse settings, including those with limited computational capabilities.
All the below efforts aim to maintain and enhance the accuracy and reliability of the malaria diagnostic tool in the face of changing real-world conditions.
/
Hypothetical Dollar Cost-Benefit Analysis:
Cost Assumptions and Estimations:
Benefit Assumptions and estimations:
10-Year Profit Estimation Calculation: Over 10 years, assuming the savings and costs remain constant, and without adjusting for inflation:
. Total Costs = Development (500,000) + Maintenance (10 years 75,000) + Training (50,000 + 9 years 10,000) = 1,250,000
. Total Benefits = Efficiency Savings (10 years 547,500) + Hospital Stay Reductions (10 years 200,000) + Labor Savings (10 years * 60,000) = 8,075,000
The estimated net profit would be Total Benefits - Total Costs = 8,075,000 - 1,250,000 = 6,825,000 dollars over 10 years.
Rationale Note: These estimates are based on researched facts about the impact of automation and AI in healthcare, such as increased diagnostic speeds, cost savings from labor reduction, and improved patient outcomes due to earlier intervention.
d. Potential risks or challenges:
Note: the accuracy of the model in real-world settings may differ from test conditions.
Business Risks:
e. Further analysis and associated problems:
.
Concluding Remark:
This model, with continuous improvement and integration into healthcare systems, has the potential to significantly enhance malaria diagnosis, treatment, and management, particularly in regions where the disease is prevalent and resources are limited.
Train the base model with different splits:
Use Cross-Validation:
Hyperparameter Tuning:
Explore more complex architectures or regularization techniques to enhance the model's ability to learn from the data without overfitting.
Ensemble Methods to combine the predictions from different models to improve performance and stability.
Outlier Detection:
Domain Expert Involvement:
External Validation: